package com.catchman.quartz;

import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import java.util.Map;

import static org.quartz.TriggerKey.triggerKey;
import static org.quartz.impl.matchers.GroupMatcher.groupEquals;

/**
 * quartz2.3 定时任务管理类
 * 0、调度程序scheduler默认没有启动(注入scheduler没有启动)，增加任务后需要手动启动scheduler，即需要调用startScheduler();
 * 1、一个任务JOB可以添加到多个Trigger 但是一个Trigger只能绑定一个JOB;
 * 2、如果一个任务只有一个触发器时，通常“触发器名”=“任务名”；
 * 3、本管理类中，如果不需要设置triggerName参数时，triggerName均是默认为jobName
 *
 * @author catchman
 */
@Component
public class QuartzManager {
    /**
     * 默认任务组名
     */
    private static String DEFAULT_JOB_GROUP_NAME = "CATCHMAN_DEFAULT_JOB";
    /**
     * 默认触发器组名字
     */
    private static String DEFAULT_TRIGGER_GROUP_NAME = "CATCHMAN_DEFAULT_TRIGGER";
    /**
     * 注入任务调度器
     */
    @Autowired
    @Qualifier("CatchManQuartzScheduler")
    private Scheduler scheduler;

    /**
     * 添加一个定时任务，使用默认的任务组名，触发器名，触发器组名
     *
     * @param jobName 任务名
     * @param cls     任务
     * @param time    时间设置，参考quartz说明文档
     */
    public void addJob(String jobName, Class<? extends Job> cls, String time) throws SchedulerException {
        addJob(jobName, DEFAULT_JOB_GROUP_NAME, jobName, DEFAULT_TRIGGER_GROUP_NAME, cls, time, null);
    }

    /**
     * 添加一个定时任务，使用默认的任务组名，触发器名，触发器组名
     *
     * @param jobName  任务名
     * @param clazzStr 任务
     * @param time     时间设置，参考quartz说明文档
     */
    public void addJob(String jobName, String clazzStr, String time) throws ClassNotFoundException, SchedulerException {
        this.addJob(jobName, (Class<? extends Job>) Class.forName(clazzStr), time);
    }


    /**
     * 添加一个定时任务，使用默认的任务组名，触发器名，触发器组名  （带参数）
     *
     * @param jobName 任务名
     * @param cls     任务
     * @param time    时间设置，参考quartz说明文档
     */
    public void addJob(String jobName, Class<? extends Job> cls, String time, Map<String, Object> parameter) throws SchedulerException {
        addJob(jobName, DEFAULT_JOB_GROUP_NAME, jobName, DEFAULT_TRIGGER_GROUP_NAME, cls, time, parameter);
    }

    /**
     * 添加一个定时任务，使用默认的 触发器名，触发器组名
     *
     * @param jobName  任务名
     * @param clazzStr 任务
     * @param time     时间设置，参考quartz说明文档
     */
    public void addJob(String jobName, String jobGroupName, String clazzStr, String time) throws SchedulerException, ClassNotFoundException {
        addJob(jobName, jobGroupName, (Class<? extends Job>) Class.forName(clazzStr), time);
    }

    /**
     * 添加一个定时任务，使用默认的 触发器名，触发器组名
     *
     * @param jobName  任务名
     * @param jobClass 任务
     * @param time     时间设置，参考quartz说明文档
     */
    public void addJob(String jobName, String jobGroupName, Class<? extends Job> jobClass, String time) throws SchedulerException, ClassNotFoundException {
        addJob(jobName, jobGroupName, jobName, DEFAULT_TRIGGER_GROUP_NAME, jobClass, time, null);
    }

    /**
     * 添加一个定时任务  （带参数）
     *
     * @param jobName          任务名
     * @param jobGroupName     任务组名
     * @param triggerName      触发器名
     * @param triggerGroupName 触发器组名
     * @param jobClass         任务
     * @param time             时间设置，参考quartz说明文档
     * @param parameter        参数（没有时设置为 null）
     */
    public void addJob(String jobName, String jobGroupName,
                       String triggerName, String triggerGroupName, Class<? extends Job> jobClass,
                       String time, Map<String, Object> parameter) throws SchedulerException {
        // 任务名，任务组，任务执行类
        JobDetail jobDetail = JobBuilder.newJob(jobClass)
                .withIdentity(jobName, jobGroupName)
                // durable, 指明任务就算没有绑定Trigger仍保留在Quartz的JobStore中
                .storeDurably()
                .build();
        //传参数
        if (parameter != null && !parameter.isEmpty()) {
            jobDetail.getJobDataMap().put("parameterList", parameter);
        }
        // 触发器
        CronTrigger trigger = TriggerBuilder
                .newTrigger()
                .withIdentity(triggerName, triggerGroupName)
                .startNow()
                .withSchedule(CronScheduleBuilder.cronSchedule(time))
                .build();

        scheduler.scheduleJob(jobDetail, trigger);
    }


    /**
     * 修改一个任务的触发时间--方式一：先移除任务再添加任务  (使用默认的触发器名，触发器组名)
     *
     * @param jobName 任务名
     * @param time    新的时间设置
     */
    public void modifyJobTimeWithJobGroupName(String jobName, String jobGroupName, String time) throws SchedulerException, ClassNotFoundException {
        //通过触发器名和组名获取TriggerKey
        TriggerKey triggerKey = triggerKey(jobName, DEFAULT_TRIGGER_GROUP_NAME);
        if (!scheduler.checkExists(triggerKey)) {
            throw new NullPointerException("the triggerKey does not exist!");
        }
        //通过TriggerKey获取CronTrigger
        CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
        if (trigger == null) {
            return;
        }
        String oldTime = trigger.getCronExpression();
        if (!oldTime.equalsIgnoreCase(time)) {
            // 先移除再增加任务
            //通过任务名和组名获取JobKey
            JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
            JobDetail jobDetail = scheduler.getJobDetail(jobKey);
            Class<? extends Job> objJobClass = jobDetail.getJobClass();
            removeJob(jobName, jobGroupName);
            addJob(jobName, jobGroupName, objJobClass, time);
        }
    }


    /**
     * 修改一个任务的触发时间--方式二：修改原触发器
     *
     * @param triggerName      任务名称
     * @param triggerGroupName 传过来的任务组名称
     * @param time             更新后的时间规则
     */
    public void modifyJobTime(String triggerName, String triggerGroupName, String time) throws SchedulerException {
        //通过触发器名和组名获取TriggerKey
        TriggerKey triggerKey = triggerKey(triggerName, triggerGroupName);
        if (!scheduler.checkExists(triggerKey)) {
            throw new NullPointerException("the triggerKey does not exist!");
        }
        //通过TriggerKey获取CronTrigger
        CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
        if (trigger == null) {
            return;
        }
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(trigger.getCronExpression());
        String oldTime = trigger.getCronExpression();
        if (!oldTime.equalsIgnoreCase(time)) {
            //重新构建trigger
            trigger = trigger.getTriggerBuilder()
                    .withIdentity(triggerKey)
                    .withSchedule(scheduleBuilder)
                    .withSchedule(CronScheduleBuilder.cronSchedule(time))
                    .build();
            // tell the scheduler to remove the old trigger with the given key, and put the new one in its place
            scheduler.rescheduleJob(triggerKey, trigger);
        }

    }

    /**
     * Listing Jobs in the Scheduler
     *
     * @throws SchedulerException
     */
    public void listAllJobs() throws SchedulerException {
        // enumerate each job group
        for (String group : scheduler.getJobGroupNames()) {
            // enumerate each job in group
            for (JobKey jobKey : scheduler.getJobKeys(groupEquals(group))) {
                System.out.println("Found job identified by: " + jobKey);
            }
        }
    }

    /**
     * Listing all Triggers in the scheduler
     *
     * @throws SchedulerException
     */
    public void listAllTriggers() throws SchedulerException {
        // enumerate each trigger group
        for (String group : scheduler.getTriggerGroupNames()) {
            // enumerate each trigger in group
            for (TriggerKey triggerKey : scheduler.getTriggerKeys(groupEquals(group))) {
                System.out.println("Found trigger identified by: " + triggerKey);
            }
        }
    }

    /**
     * 移除一个任务(使用默认的任务组名)
     *
     * @param jobName 任务名称
     */
    public void removeJob(String jobName) throws SchedulerException {
        removeJob(jobName, DEFAULT_JOB_GROUP_NAME);
    }

    /**
     * 移除一个任务，包括所有触发器
     *
     * @param jobName 任务名称
     */
    public void removeJob(String jobName, String jobGroupName) throws SchedulerException {
        //通过触发器名和组名获取TriggerKey
        JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
        if (!scheduler.checkExists(jobKey)) {
            throw new NullPointerException("the triggerKey does not exist!");
        }
        // Deleting a Job and Unscheduling All of Its Triggers
        scheduler.deleteJob(jobKey);
    }

    /**
     * 移除指定触发器，默认触发器组
     *
     * @param triggerName 触发器名
     */
    public void removeTrigger(String triggerName) throws SchedulerException {
        removeTrigger(triggerName, DEFAULT_TRIGGER_GROUP_NAME);
    }

    /**
     * 移除指定触发器
     *
     * @param triggerName      触发器名
     * @param triggerGroupName 触发器组名
     */
    public void removeTrigger(String triggerName, String triggerGroupName) throws SchedulerException {
        //通过触发器名和组名获取TriggerKey
        TriggerKey triggerKey = triggerKey(triggerName, triggerGroupName);
        if (!scheduler.checkExists(triggerKey)) {
            throw new NullPointerException("the triggerKey does not exist!");
        }
        scheduler.unscheduleJob(triggerKey);
    }

    /**
     * 获取job状态 ，默认触发器组
     *
     * @param jobName 任务名
     */
    public Trigger.TriggerState getJobStatus(String jobName) throws SchedulerException {
        return getJobStatus(jobName, DEFAULT_TRIGGER_GROUP_NAME);
    }

    /**
     * 获取job状态，因为一个触发器只能调用一个任务，因此获取触发器状态就是获取任务状态
     *
     * @param triggerName      触发器名
     * @param triggerGroupName 触发器组名
     */
    public Trigger.TriggerState getJobStatus(String triggerName, String triggerGroupName) throws SchedulerException {
        //通过触发器名和组名获取TriggerKey
        TriggerKey triggerKey = triggerKey(triggerName, triggerGroupName);
        if (!scheduler.checkExists(triggerKey)) {
            throw new NullPointerException("the triggerKey does not exist!");
        }
        return scheduler.getTriggerState(triggerKey);
    }

    /**
     * 前提是必须已经增加（addJob）过该任务
     * 立即执行指定任务
     *
     * @param jobName      任务名
     * @param jobGroupName 任务组名
     */
    public void triggerJob(String jobName, String jobGroupName) throws SchedulerException {
        JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
        if (!scheduler.checkExists(jobKey)) {
            throw new NullPointerException("the job does not exist!");
        }
        scheduler.triggerJob(jobKey);
    }


    /**
     * 启动调度程序
     * 必须首先启动调度程序，否则无法执行任务
     *
     * @throws SchedulerException 如果已经被shutdown 则报错
     */
    public void startScheduler() throws SchedulerException {
        if (!scheduler.isShutdown()) {
            scheduler.start();      // 启动
        }
    }


    /**
     * 关闭调度程序
     * 关闭后无法被重新启动。如果需要重启请调用 standby
     *
     * @throws SchedulerException
     */
    public void shutdownScheduler() throws SchedulerException {
        if (!scheduler.isShutdown()) {
            scheduler.shutdown(); //Do Not Wait for Executing Jobs to Finish
            //scheduler.shutdown(true); //Wait for Executing Jobs to Finish
        }
    }

    /**
     * 暂时停止调度程序
     * 暂停后可以被重新启动
     *
     * @throws SchedulerException
     */
    public void standByScheduler() throws SchedulerException {
        if (!scheduler.isShutdown()) {
            scheduler.standby();
        }
    }

    /**
     * 暂停一个任务，暂停该任务的所有触发器
     *
     * @param jobName
     * @param jobGroup
     */
    public void pauseJob(String jobName, String jobGroup) throws SchedulerException {
        JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
        if (!scheduler.checkExists(jobKey)) {
            throw new NullPointerException("the job does not exist!");
        }
        scheduler.pauseJob(jobKey);
    }

    /**
     * 暂停一个任务组所有任务，即使当前组没有任务，也会记住暂停状态，新增后会启动暂停
     *
     * @param jobGroup
     */
    public void pauseJobGroup(String jobGroup) throws SchedulerException {
        GroupMatcher<JobKey> matcher = groupEquals(jobGroup);
        scheduler.pauseJobs(matcher);
    }

    /**
     * 暂停所有定时任务，新增加job也会暂停
     */
    public void pauseJobs() throws SchedulerException {
        if (!scheduler.isShutdown()) {
            scheduler.pauseAll();
        }
    }

    /**
     * 恢复一个任务
     */
    public void resumeJob(String jobName, String jobGroup) throws SchedulerException {
        JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
        if (!scheduler.checkExists(jobKey)) {
            throw new NullPointerException("the job does not exist!");
        }
        scheduler.resumeJob(jobKey);
    }

    /**
     * 恢复一个任务组的所有任务
     */
    public void resumeJobGroup(String jobGroup) throws SchedulerException {
        GroupMatcher<JobKey> matcher = groupEquals(jobGroup);
        scheduler.resumeJobs(matcher);
    }

    /**
     * 恢复所有定时任务
     */
    public void resumeJobs() throws SchedulerException {
        if (!scheduler.isShutdown()) {
            scheduler.resumeAll();
        }
    }
}
